<!DOCTYPE html><html lang="zh"><head><meta charset="UTF-8"><script async src="https://www.googletagmanager.com/gtag/js?id=G-MP1THLPZFC"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-MP1THLPZFC")</script><script>var _hmt=_hmt||[];!function(){var e=document.createElement("script");e.src="https://hm.baidu.com/hm.js?956b14594152e1b71ce14695a71235d5";var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t)}()</script><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1"><meta http-equiv="X-UA-Compatible" content="ie=edge"><meta name="author" content="CS_GUIDER"><meta name="subtitle" content="If not now, when? If not me, who?"><meta name="description" content="欢迎来到我的CS_GUIDER博客！这里提供了全面、实用和最新的Java编程信息。你可以找到Java算法、项目、博客、数据库、Linux系统的教程和笔记。我致力于为您提供Java编程的实用指南和资源，包括Java框架、JVM和Git等。无论您是初学者还是经验丰富的开发人员，都可以从中获益。谢谢您的光临！"><meta name="keywords" content="Java,算法,项目,博客,数据库,Linux,框架,JVM,Git,编程,开发,教程"><title>仿哔哩哔哩项目 | CS_GUIDER&#39;S Blog</title><link rel="icon" href="/favicon.ico"><link rel="stylesheet" href="/css/main.css"><link rel="stylesheet" href="/lib/nprogress/nprogress.css"><script src="/lib/jquery.min.js"></script><script src="/lib/iconify-icon.min.js"></script><script src="https://cdn.tailwindcss.com?plugins=typography"></script><script>tailwind.config={darkMode:"class"}</script><script src="/lib/nprogress/nprogress.js"></script><script>$(document).ready(()=>{NProgress.configure({showSpinner:!1}),NProgress.start(),$("#nprogress .bar").css({background:"#de7441"}),$("#nprogress .peg").css({"box-shadow":"0 0 2px #de7441, 0 0 4px #de7441"}),$("#nprogress .spinner-icon").css({"border-top-color":"#de7441","border-left-color":"#de7441"}),setTimeout((function(){NProgress.done(),$(".fade").removeClass("out")}),800)})</script><script>!function(){const e=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches,t=localStorage.getItem("hexo-color-scheme")||"auto";("dark"===t||e&&"light"!==t)&&document.documentElement.classList.toggle("dark",!0);document.documentElement.classList.contains("dark")}(),$(document).ready((function(){const e=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches,t=document.documentElement.classList.contains("dark");function o(){const e=document.documentElement.classList.contains("dark"),t=document.querySelector("iframe.giscus-frame");t&&t.contentWindow.postMessage({giscus:{setConfig:{theme:e?"dark":"light"}}},"https://giscus.app")}function n(){let t=document.documentElement.classList.contains("dark");localStorage.getItem("hexo-color-scheme");t=!t,document.documentElement.classList.toggle("dark",t),$("#theme-icon").attr("icon",t?"ri:moon-line":"ri:sun-line"),e===t?localStorage.setItem("hexo-color-scheme","auto"):localStorage.setItem("hexo-color-scheme",t?"dark":"light"),o()}$("#theme-icon").attr("icon",t?"ri:moon-line":"ri:sun-line"),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",e=>{"auto"===(localStorage.getItem("hexo-color-scheme")||"auto")&&(document.documentElement.classList.toggle("dark",e.matches),$("#theme-icon").attr("icon",e.matches?"ri:moon-line":"ri:sun-line"),o())}),$("#toggle-dark").click(e=>{if(!(document.startViewTransition&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches))return void n();const t=e.clientX,o=e.clientY,c=Math.hypot(Math.max(t,innerWidth-t),Math.max(o,innerHeight-o));document.startViewTransition(async()=>{n()}).ready.then(()=>{const e=document.documentElement.classList.contains("dark"),n=[`circle(0px at ${t}px ${o}px)`,`circle(${c}px at ${t}px ${o}px)`];document.documentElement.animate({clipPath:e?[...n].reverse():n},{duration:400,easing:"ease-out",pseudoElement:e?"::view-transition-old(root)":"::view-transition-new(root)"})})})}))</script><meta name="generator" content="Hexo 5.4.2"></head><body class="font-sans bg-white dark:bg-zinc-900 text-gray-700 dark:text-gray-200 relative"><header class="fixed w-full px-5 py-1 z-10 backdrop-blur-xl backdrop-saturate-150 border-b border-black/5"><div class="max-auto"><nav class="flex items-center text-base"><a href="/" class="group"><h2 class="font-medium tracking-tighterp text-l p-2"><img class="w-5 mr-2 inline-block transition-transform group-hover:rotate-[30deg]" id="logo" src="/img/logo.svg" alt="CS_GUIDER'S Blog"> CS_GUIDER&#39;S Blog</h2></a><div id="header-title" class="opacity-0 md:ml-2 md:mt-[0.1rem] text-xs font-medium whitespace-nowrap overflow-hidden overflow-ellipsis">仿哔哩哔哩项目</div><div class="flex-1"></div><div class="flex items-center gap-3"><a class="hidden sm:flex" href="/">Home</a> <a class="hidden sm:flex" href="/archives">Archives</a> <a class="hidden sm:flex" href="/category">Categories</a> <a class="hidden sm:flex" href="/tag">Tags</a> <a class="w-5 h-5 hidden sm:flex" title="Github" target="_blank" rel="noopener" href="https://github.com/wl2o2o"><iconify-icon width="20" icon="ri:github-line"></iconify-icon></a><a class="w-5 h-5 hidden sm:flex" title="Github" href="rss2.xml"><iconify-icon width="20" icon="ri:rss-line"></iconify-icon></a><a class="w-5 h-5" title="toggle theme" id="toggle-dark"><iconify-icon width="20" icon="" id="theme-icon"></iconify-icon></a></div><div class="flex items-center justify-center gap-3 ml-3 sm:hidden"><span class="w-5 h-5" aria-hidden="true" role="img" id="open-menu"><iconify-icon width="20" icon="carbon:menu"></iconify-icon></span><span class="w-5 h-5 hidden" aria-hidden="true" role="img" id="close-menu"><iconify-icon width="20" icon="carbon:close"></iconify-icon></span></div></nav></div></header><div id="menu-panel" class="h-0 overflow-hidden sm:hidden fixed left-0 right-0 top-12 bottom-0 z-10"><div id="menu-content" class="relative z-20 bg-white/80 px-6 sm:px-8 py-2 backdrop-blur-xl -translate-y-full transition-transform duration-300"><ul class="nav flex flex-col sm:flex-row text-sm font-medium"><li class="nav-portfolio sm:mx-2 border-b sm:border-0 border-black/5 last:border-0 hover:text-main"><a href="/" class="flex h-12 sm:h-auto items-center">Home</a></li><li class="nav-portfolio sm:mx-2 border-b sm:border-0 border-black/5 last:border-0 hover:text-main"><a href="/archives" class="flex h-12 sm:h-auto items-center">Archives</a></li><li class="nav-portfolio sm:mx-2 border-b sm:border-0 border-black/5 last:border-0 hover:text-main"><a href="/category" class="flex h-12 sm:h-auto items-center">Categories</a></li><li class="nav-portfolio sm:mx-2 border-b sm:border-0 border-black/5 last:border-0 hover:text-main"><a href="/tag" class="flex h-12 sm:h-auto items-center">Tags</a></li></ul></div><div class="mask bg-black/20 absolute inset-0"></div></div><main class="pt-14"><link rel="stylesheet" href="/lib/fancybox/fancybox.min.css"><link rel="stylesheet" href="/lib/tocbot/tocbot.min.css"><nav class="post-toc toc text-sm w-48 relative top-32 right-0 opacity-70 hidden lg:block" style="position:fixed!important"></nav><section class="px-6 max-w-prose mx-auto md:px-0"><header class="overflow-hidden pt-6 pb-6 md:pt-12"><div class="pt-4 md:pt-6"><h1 id="article-title" class="text-[2rem] font-bold leading-snug mb-4 md:mb-6 md:text-[2.6rem]">仿哔哩哔哩项目</h1><div><section class="flex items-center gap-3 text-sm"><span class="flex items-center gap-1"><iconify-icon width="18" icon="carbon-calendar"></iconify-icon><time>2025-09-17</time> </span><span class="text-gray-400">·</span> <span class="flex items-center gap-1"><iconify-icon width="18" icon="ic:round-access-alarm"></iconify-icon><span>20 min</span> </span><span class="text-gray-400">·</span> <span class="flex items-center gap-1"><iconify-icon width="18" icon="icon-park-outline:font-search"></iconify-icon><span>4.8k words</span> </span><span class="text-gray-400">·</span> <span class="flex items-center gap-1"><iconify-icon width="16" icon="icon-park-outline:box" class="mr-2"></iconify-icon><a class="article-category-link" href="/categories/Project/">Project</a></span></section></div></div></header><article class="post-content prose m-auto slide-enter-content dark:prose-invert"><h3 id="从项目角度和技术角度两个维度来看："><a href="#从项目角度和技术角度两个维度来看：" class="headerlink" title="从项目角度和技术角度两个维度来看："></a>从项目角度和技术角度两个维度来看：</h3><blockquote><p>项目角度：规模大、不同种类的用户群体、高流量、个性化功能针对不同的用户；</p><p>技术角度：经典高并发与异步问题、视频流+弹幕定制化功能。</p></blockquote><h3 id="项目大纲：（课程链接）"><a href="#项目大纲：（课程链接）" class="headerlink" title="项目大纲：（课程链接）"></a>项目大纲：（<a target="_blank" rel="noopener" href="https://coding.imooc.com/class/556.html">课程链接</a>）</h3><blockquote><p>第一章：项目整体介绍、课程设计逻辑、学习方法</p><p>第二章：项目架构、环境搭建、效果展示</p><p>第三章：通用配置、用户相关功能</p><p>第四章：视频流+弹幕加载、性能优化</p><p>第五章：全局搜索、系统广播、数据统计、智能推荐</p><p>第六章：总结复盘、切面编程、自动化部署、负载均衡</p></blockquote><h2 id="从搭建环境开始你的仿哔哩哔哩项目（初入江湖）"><a href="#从搭建环境开始你的仿哔哩哔哩项目（初入江湖）" class="headerlink" title="从搭建环境开始你的仿哔哩哔哩项目（初入江湖）"></a>从搭建环境开始你的仿哔哩哔哩项目（初入江湖）</h2><h3 id="项目架构："><a href="#项目架构：" class="headerlink" title="项目架构："></a><strong>项目架构：</strong></h3><p>基本过程：需求分析–》功能设计–》全局架构（承载、可复用）</p><h3 id="业务（功能架构）："><a href="#业务（功能架构）：" class="headerlink" title="业务（功能架构）："></a><strong>业务（功能架构）：</strong></h3><p>顶层：用户服务，如注册登录、大会员权限、查找感兴趣视频等</p><p>中间层：在线视频播放设置、实施弹幕</p><p>底层：管理后台，如：视频上传、数据统计、系统消息推送</p><h3 id="技术架构："><a href="#技术架构：" class="headerlink" title="技术架构："></a><strong>技术架构：</strong></h3><p>技术选型：<code>SpringBoot2.x</code>+ <code>Mysql</code> + <code>MyBatis</code> + <code>Maven</code></p><p>开发模式：项目采用经典<code>MVC</code>，模式控制层（<code>Controller层</code>）、服务层（<code>Service层</code>）、数据层（<code>Dao层</code>）</p><h3 id="部署架构"><a href="#部署架构" class="headerlink" title="部署架构:"></a><strong>部署架构:</strong></h3><p>前端：服务转发 + 负载均衡</p><p>后端：业务处理 + 功能实现</p><p>工具：缓存 + 队列</p><h3 id="开发环境："><a href="#开发环境：" class="headerlink" title="开发环境："></a><strong>开发环境：</strong></h3><p><code>OS</code>：<code>Java</code>的跨平台，任意OS即可</p><p>开发工具：<code>IntelliJ IDEA</code>，推荐版本2018及以后</p><p>必备：<code>JDK1.8</code>、<code>Maven</code></p><h3 id="创建多模块、多环境项目："><a href="#创建多模块、多环境项目：" class="headerlink" title="创建多模块、多环境项目："></a><strong>创建多模块、多环境项目：</strong></h3><p>多模块：经典MVC</p><p>多环境：添加不同的properties配置文件（测试、部署）</p><p>配置好项目的JDK版本与Maven仓库</p><h3 id="项目运行"><a href="#项目运行" class="headerlink" title="项目运行:"></a><strong>项目运行:</strong></h3><p>在<code>pom.xml</code>文件中加入<code>SpringBoot</code>框架依赖:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.5.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.5.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在子模块<code>pom.xml</code>文件中添加模块间依赖关系:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--配置模块间的依赖关系--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.example<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>imooc-bilibili-dao<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>添加启动入口，启动项目：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.SpringApplication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.ApplicationContext;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> WLei224</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@create</span> 2023/4/28 21:34</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ImoocBilibiliApp</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ApplicationContext</span> <span class="variable">app</span> <span class="operator">=</span> SpringApplication.run(ImoocBilibiliApp.class, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>多环境配置：</p><p>在Service包下创建一个<code>application.properties</code>配置文件:</p><p>在Controller包下创建多个生产环境：<code>application-test.properties</code>、<code>application-online.properties</code></p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#profiles可用于切换生产环境</span></span><br><span class="line"><span class="attr">spring.profiles.active</span>=<span class="string">test</span></span><br></pre></td></tr></table></figure><h3 id="数据库的搭建与持久层框架："><a href="#数据库的搭建与持久层框架：" class="headerlink" title="数据库的搭建与持久层框架："></a><strong>数据库的搭建与持久层框架：</strong></h3><p>配置<code>MySQL</code>数据库:</p><p>引入<code>MySQL</code>数据库和持久层<code>Mybatis</code>依赖：（<code>Mybatis</code>特点：XML形式管理，支持动态<code>sql</code>）</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>8.0.27<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span>     		     </span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis.spring.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.2.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>将数据库连接写入<code>application.properties</code>配置文件：</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#datasource</span></span><br><span class="line"><span class="attr">spring.datasource.url</span>=<span class="string">jdbc:mysql://localhost:3306/imooc_bilibili</span></span><br><span class="line"><span class="attr">spring.datasource.username</span>=<span class="string">root</span></span><br><span class="line"><span class="attr">spring.datasource.password</span>=<span class="string">root(你的密码)</span></span><br><span class="line"><span class="attr">spring.datasource.driver-class-name</span>=<span class="string">com.mysql.cj.jdbc.Driver</span></span><br></pre></td></tr></table></figure><p>将<code>Mybatis</code>配置写入可复用的<code>application.properties</code>配置文件中：</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#mybatis</span></span><br><span class="line"><span class="attr">mybatis.mapper-locations</span>=<span class="string">classpath:mapper/*.xml</span></span><br><span class="line"><span class="comment">#项目启动时，告诉SpringBoot扫描class、interface的路径，统一实例化，然后与mapper进行关联</span></span><br><span class="line"><span class="attr">mybatis.type-aliases-package</span>=<span class="string">com.imooc.bilibili.dao</span></span><br></pre></td></tr></table></figure><p><strong>开发一个小Demo：</strong>（在持久层<code>Dao</code>层进行）</p><p>首先要将<code>mapper</code>与<code>dao</code>层实体类进行关联：(<code>Demo.xml</code>)</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">mapper</span> <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD MAPPER 3.0//EN&quot;</span></span></span><br><span class="line"><span class="meta">        <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--namespace对应着dao层Java实体类文件--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;com.imooc.bilibili.dao.DemoDao&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;query&quot;</span> <span class="attr">parameterType</span>=<span class="string">&quot;java.lang.Long&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;java.lang.Long&quot;</span>&gt;</span></span><br><span class="line">        select id from t_demo where id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>Controller:</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.imooc.bilibili.api;</span><br><span class="line"><span class="keyword">import</span> com.imooc.bilibili.service.DemoService;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> WLei224</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@create</span> 2023/4/29 1:50</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoApi</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DemoService demoService;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/query&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">query</span><span class="params">(Long id)</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> demoService.query(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Service:</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.imooc.bilibili.service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.imooc.bilibili.dao.DemoDao;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> WLei224</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@create</span> 2023/4/29 1:33</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DemoDao demoDao;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">query</span><span class="params">(Long id)</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> demoDao.query(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Dao:</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.imooc.bilibili.dao;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.annotations.Mapper;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> WLei224</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@create</span> 2023/4/29 1:04</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">//如何将DemoDao与mapper对应起来呢？ 为什么声明成为接口呢?因为@mapper注解在启动时会自动匹配，把dao的文件封装成一个实体类，从而实现自动实例化的操作</span></span><br><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DemoDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">query</span><span class="params">(Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="效率提升：实现热部署："><a href="#效率提升：实现热部署：" class="headerlink" title="效率提升：实现热部署："></a>效率提升：实现热部署：</h3><blockquote><p>热部署：热部署就是当应用程序正在运行的时候升级软件或修改某一部分代码、配置文件时，无需手动重启应用，即可使修改的部分生效</p><p>配置方法：<code>spring-boot-devtools</code>依赖工具+IDEA配置</p></blockquote><p>1、<code>IDEA:</code>Files–&gt;Settings–&gt;Compiles–&gt;Build Project Automately</p><p>2、<code>IDEA注册表：Ctrl+Alt+Shift+/</code>打开<code>compiler document save enabled</code>和<code>compiler automake allow when app runing</code></p><p>3、编辑启动类的配置：<img src="http://images.rl0206.love/202304291326170.png" alt="image-20230429132152997"></p><p>4、引入全局<code>pom.xml</code>依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 热部署 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-devtools<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.0.4.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">     <span class="comment">&lt;!-- 启用 --&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>热部署已完成。</p><h2 id="3从用户功能体验后端经典开发模式（窥得门路）"><a href="#3从用户功能体验后端经典开发模式（窥得门路）" class="headerlink" title="3从用户功能体验后端经典开发模式（窥得门路）"></a>3从用户功能体验后端经典开发模式（窥得门路）</h2><h3 id="用户模块开发概要与接口设计"><a href="#用户模块开发概要与接口设计" class="headerlink" title="用户模块开发概要与接口设计"></a>用户模块开发概要与接口设计</h3><blockquote><p><strong><code>RESTful</code>风格接口设计：</strong></p><p>RESTful架构、HTTP方法语义、HTTP方法幂等性、RESTful接口设计原则</p><p><strong>用户模块开发概要：</strong>通用功能与通用配置、用户相关功能</p></blockquote><h3 id="RESTful接口"><a href="#RESTful接口" class="headerlink" title="RESTful接口"></a>RESTful接口</h3><blockquote><p>REST全称是：Representational State Transfer，中文为表述性状态转移，REST指的是一组架构约束条件和原则</p><p>RESTful表述的是资源的状态转移，在Web中资源就是URI(Uniform Resource Identifier)</p><p>如果一个架构符合REST的约束条件和原则，我们就称它为RESTful架构，HTTP是目前与REST相关的唯一实例</p><p>RESTful架构应该遵循统一的接口原则，应该使用标准的HTTP方法，如GET和POST，并且遵循这些方法的语义</p></blockquote><h3 id="HTTP方法的语义"><a href="#HTTP方法的语义" class="headerlink" title="HTTP方法的语义"></a>HTTP方法的语义</h3><p><img src="http://images.rl0206.love/202304291643114.png" alt="image-20230429164253420"></p><h3 id="POST和PUT的区别"><a href="#POST和PUT的区别" class="headerlink" title="POST和PUT的区别"></a>POST和PUT的区别</h3><p>这两个概念非常容易混淆，POST通常被认为创建资源，PUT通常被认为更新资源，而实际上，二者均可用于创建资源，更为本质的差别实在幂等性方面。</p><blockquote><p>所谓幂等性，如果一个操作执行一次和执行多次的后果是一样的，那么这个操作就具有幂等性。</p><p>例如：GET获取多次， 无副作用， 具有幂等性</p><p>​ DELETE删除多次，无副作用， 具有幂等性</p><p>​ POST提交会创建不同的资源， 不具有幂等性（实例如下图）</p><p>​ PUT是创建或更新，无副作用， 具有幂等性</p></blockquote><p><img src="http://images.rl0206.love/202304291651182.png" alt="image-20230429165143109"></p><p><code>Demo：RESTfulApi:</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.imooc.bilibili.api;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.*;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> WLei224</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@create</span> 2023/4/30 11:01</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RESTfulApi</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Integer,Map&lt;String,Object&gt;&gt; dataMap;</span><br><span class="line">    <span class="comment">// 声明一个构造方法，同时初始化datamap，进行传参</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RESTfulApi</span><span class="params">()</span> &#123;</span><br><span class="line">        dataMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; <span class="number">3</span>; i++) &#123;</span><br><span class="line">            Map&lt;String,Object&gt; data = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">            data.put(<span class="string">&quot;id&quot;</span>,i);</span><br><span class="line">            data.put(<span class="string">&quot;name&quot;</span>,<span class="string">&quot;name&quot;</span>+i);</span><br><span class="line">            dataMap.put(i,data);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 开始写RESTful的相关方法</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;objects/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Map&lt;String,Object&gt; <span class="title function_">getData</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> dataMap.get(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@DeleteMapping(&quot;objects/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">deleteData</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span>&#123;</span><br><span class="line">        dataMap.remove(id);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Success&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;objects&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">postData</span><span class="params">(<span class="meta">@RequestBody</span> Map&lt;String,Object&gt; data)</span>&#123;</span><br><span class="line">        Integer[] idArray = dataMap.keySet().toArray(<span class="keyword">new</span> <span class="title class_">Integer</span>[<span class="number">0</span>]);</span><br><span class="line">        Arrays.sort(idArray);</span><br><span class="line">        <span class="type">int</span> <span class="variable">nextId</span> <span class="operator">=</span> idArray[idArray.length-<span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">        <span class="comment">// data.put(&quot;id&quot;,nextId);</span></span><br><span class="line">        <span class="comment">// data.put(&quot;name&quot;,&quot;name&quot; + nextId);</span></span><br><span class="line">        dataMap.put(nextId,data);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Success!&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">// 区别就在于幂等性，存在则更新，不存在则新增</span></span><br><span class="line">    <span class="meta">@PutMapping(&quot;objects&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">putData</span><span class="params">(<span class="meta">@RequestBody</span> Map&lt;String,Object&gt; data)</span>&#123;</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">id</span> <span class="operator">=</span> Integer.valueOf(String.valueOf(data.get(<span class="string">&quot;id&quot;</span>)));</span><br><span class="line">        Map&lt;String,Object&gt; hasData = dataMap.get(id);</span><br><span class="line">        <span class="keyword">if</span> (hasData == <span class="literal">null</span>) &#123;</span><br><span class="line">            Integer[] idArray = dataMap.keySet().toArray(<span class="keyword">new</span> <span class="title class_">Integer</span>[<span class="number">0</span>]);</span><br><span class="line">            Arrays.sort(idArray);</span><br><span class="line">            <span class="type">int</span> <span class="variable">nextId</span> <span class="operator">=</span> idArray[idArray.length-<span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">            <span class="comment">// data.put(&quot;id&quot;,nextId);</span></span><br><span class="line">            <span class="comment">// data.put(&quot;name&quot;,&quot;name&quot; + nextId);</span></span><br><span class="line">            dataMap.put(nextId,data);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            dataMap.put(id,data);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Success!&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="RESTful接口URL命名原则："><a href="#RESTful接口URL命名原则：" class="headerlink" title="RESTful接口URL命名原则："></a>RESTful接口URL命名原则：</h3><blockquote><p>1、HTTP方法后跟的URL必须是名词的复数形式</p><p>2、URL总不采用大小写混合的驼峰命名，尽量全部小写，如果涉及多个单词，可用”-“连接</p><p>3、示例：/users、/users-fans、 反例：/getUser、/getUserFans</p></blockquote><h3 id="RESTful接口URL分级原则"><a href="#RESTful接口URL分级原则" class="headerlink" title="RESTful接口URL分级原则"></a>RESTful接口URL分级原则</h3><blockquote><p>1、一级用来定位资源分类，如：/users表示需要定位到用户相关资源</p><p>2、二级仍用来定位具体某个资源，如：/users/20/fans/1表示id为20的用户的id为1的粉丝</p></blockquote><h3 id="RESTful接口命名示例"><a href="#RESTful接口命名示例" class="headerlink" title="RESTful接口命名示例"></a>RESTful接口命名示例</h3><p><img src="http://images.rl0206.love/202304301533812.png" alt="image-20230430153315090"></p><p><img src="http://images.rl0206.love/202304301534235.png" alt="image-20230430153406940"></p><h3 id="通用功能与配置"><a href="#通用功能与配置" class="headerlink" title="通用功能与配置"></a>通用功能与配置</h3><h4 id="通用功能："><a href="#通用功能：" class="headerlink" title="通用功能："></a><strong>通用功能：</strong></h4><p>加解密工具（<code>AES</code>、<code>RSA</code>、<code>MD5</code>）、json数据返回类</p><p>顶层POM.xml添加commons-codec依赖，</p><p>添加对应的工具包到service包的util包下</p><blockquote><p><code>什么是AES加密</code></p></blockquote><p><code>AES:</code></p><p><em>Advanced Encryption Standard</em>高级加密标准，是最常见的对称加密算法，对称加密即加解密只有一个密钥，可使用密钥恢复明文，加密速度非常快。</p><p><code>使用场景：</code></p><p>适合发送大量数据的场合。</p><p><code>看下源码：</code></p><p><img src="http://images.rl0206.love/202305191736417.png" alt="image-20230519170635001"></p><blockquote><p><code>什么是RSA加密？</code></p></blockquote><p><code>RSA:</code></p><p>是一种非对称加密，即：有公钥与私钥之分，公钥用于数据加密，私钥用于数据解密，同样是可逆的，即可以通过私钥进行解密。公钥提供给外部进行使用，私钥放在服务器，保护数据安全。</p><p><code>特点：</code></p><p>加密安全性很高，但是加密速度非常之慢。</p><p><code>使用场景：</code></p><p>由特点可知，加密慢，但是安全。因此适合对加密次数要求较少的场景。例如：用户的登陆，加密一次，便不用加密，而且安全性还较高。</p><p><code>拓展：非对称加密的流程是什么，在实际应用中是如何进行加密的？</code></p><p>下面以用户注册登录场景为例，来说一下非对称加密在实际中的应用：</p><p>因为RSA加密中的公钥是提供给外部进行加密使用的，用户在前端注册登录时，为了保证输入的密码其安全性（防止拦截后密码泄露），将公钥返回到前端，前端使用公钥进行加密，加密后的暗文通过接口然后传给后端，后端再通过私钥进行解密，得到密码。</p><p><code>加解密源码：</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">encrypt</span><span class="params">(String source)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">	<span class="type">byte</span>[] decoded = Base64.decodeBase64(PUBLIC_KEY);</span><br><span class="line">	<span class="type">RSAPublicKey</span> <span class="variable">rsaPublicKey</span> <span class="operator">=</span> (RSAPublicKey) KeyFactory.getInstance(<span class="string">&quot;RSA&quot;</span>)</span><br><span class="line">			.generatePublic(<span class="keyword">new</span> <span class="title class_">X509EncodedKeySpec</span>(decoded));</span><br><span class="line">	<span class="type">Cipher</span> <span class="variable">cipher</span> <span class="operator">=</span> Cipher.getInstance(<span class="string">&quot;RSA&quot;</span>);</span><br><span class="line">	cipher.init(<span class="number">1</span>, rsaPublicKey);</span><br><span class="line">	<span class="keyword">return</span> Base64.encodeBase64String(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8)));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">decrypt</span><span class="params">(String text)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">	<span class="type">Cipher</span> <span class="variable">cipher</span> <span class="operator">=</span> getCipher();</span><br><span class="line">	<span class="type">byte</span>[] inputByte = Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>(cipher.doFinal(inputByte));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>最后说一下MD5加密：</code></p><p><code>MD5：</code></p><p>非对称加密，即不可逆，无法看到加密前的明文。</p><p><code>特点：</code></p><p>加密速度快，无需密钥，但是安全性不高需要搭配随机盐值使用。随机盐就是一个随机数，防止黑客将加密后的MD5还原回去。</p><h4 id="通用配置："><a href="#通用配置：" class="headerlink" title="通用配置："></a><strong>通用配置：</strong></h4><p><code>Json信息转换配置 &amp;&amp; 全局异常处理配置</code></p><blockquote><p><code>JSON返回数据配置：</code></p></blockquote><p><code>什么是JSON？：JSON就是一种轻量化数据交换格式。</code></p><p><code>为什么会用到JSON返回数据类和数据类转换呢？因为JSON轻量化，前端需要展示不同的数据格式时，这就需要用到JSON信息转换了。</code></p><p>如何新建JSON信息转换配置？</p><p>Service包下新建config包，用于放以后所有的配置类。</p><p>此处涉及到Spring Boot相关的注解名，下面来说一下常见的注解：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Configuration：标志着Java文件是一个配置类，经常搭配@Bean使用，表示向上下文注入实体类，使其生效；</span><br><span class="line">@Component：是@Configuration注解的内部注解，在Spring Boot启动阶段，自动的将Configuration</span><br><span class="line">对应的文件注入到Sping Boot上下文；</span><br><span class="line">@Bean：表示向上下文注入实体类，使其生效；</span><br></pre></td></tr></table></figure><p><code>HttpMessageConverters：</code>是一个对Http方法，接收请求，或做转换的一个工具类框架，返回的就是一个@Bean类型，因为此方法是一个JSON类型，所以要引入一个fastJson依赖（目前世界行公认效率最高的工具包）。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>fastjson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.78<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>然后配置一些和fastjson相关的配置类。</p><p>例如：配置相关的数据返回类型的时间格式、序列化的相关配置、</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">fastJsonConfig.setSerializerFeatures(</span><br><span class="line">                <span class="comment">// 格式化输出</span></span><br><span class="line">                SerializerFeature.PrettyFormat,</span><br><span class="line">                <span class="comment">// 如果输出的数据是空的，那么系统会直接把这个数据去掉，不会在前端进行显示，这个配置项可以显示出一个空串</span></span><br><span class="line">                SerializerFeature.WriteNullStringAsEmpty,</span><br><span class="line">                <span class="comment">// 功能同上，列表</span></span><br><span class="line">                SerializerFeature.WriteNullListAsEmpty,</span><br><span class="line">                <span class="comment">// 同上，集合</span></span><br><span class="line">                SerializerFeature.WriteMapNullValue,</span><br><span class="line">                <span class="comment">// 升序排列</span></span><br><span class="line">                SerializerFeature.MapSortField,</span><br><span class="line">                <span class="comment">// 进制循环引用（防止循环引用后，输出多余的引用字符串） 非常有用的一个配置</span></span><br><span class="line">                SerializerFeature.DisableCircularReferenceDetect</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>循环引用：</p><p><img src="http://images.rl0206.love/202305201545971.png" alt="image-20230520144308894"></p><blockquote><p><code>全局异常处理配置：</code></p></blockquote><p>放在Service包下的handle包中，命名为全局异常处理类（<code>CommonGlobalExceptionHandler.class</code>）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.imooc.bilibili.service.handler;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.imooc.bilibili.dao.damain.JsonResponse;</span><br><span class="line"><span class="keyword">import</span> com.imooc.bilibili.dao.damain.exception.ConditionException;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.Ordered;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.annotation.Order;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.ControllerAdvice;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.ExceptionHandler;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.ResponseBody;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> WLei224</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@create</span> 2023/5/20 16:38</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ControllerAdvice</span></span><br><span class="line"><span class="meta">@Order(Ordered.HIGHEST_PRECEDENCE)</span> <span class="comment">// 最高优先级</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CommonGlobalExceptionHandler</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExceptionHandler(value = Exception.class)</span></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="keyword">public</span> JsonResponse&lt;String&gt; <span class="title function_">commonExceptionHandler</span><span class="params">(HttpServletRequest request, Exception e)</span>&#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">errorMsg</span> <span class="operator">=</span> e.getMessage();</span><br><span class="line">        <span class="keyword">if</span>(e <span class="keyword">instanceof</span> ConditionException)&#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">errorCode</span> <span class="operator">=</span> ((ConditionException) e).getCode();</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JsonResponse</span>&lt;&gt;(errorCode,errorMsg);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JsonResponse</span>&lt;&gt;(<span class="string">&quot;500&quot;</span>,errorMsg);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在此之前，我定义了一个条件异常，并添加了状态码等信息，然后这个类的代码功能就是，抓取条件异常信息，然后通过<code>json数据返回类型jsonResponse</code>返回异常信息。</p><h3 id="用户注册与登录"><a href="#用户注册与登录" class="headerlink" title="用户注册与登录"></a>用户注册与登录</h3><p>数据库库表设计：用户表、用户信息表</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `t_user` (</span><br><span class="line">  `id` <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT COMMENT <span class="string">&#x27;主键&#x27;</span>,</span><br><span class="line">  `phone` <span class="type">varchar</span>(<span class="number">100</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;手机号&#x27;</span>,</span><br><span class="line">  `email` <span class="type">varchar</span>(<span class="number">100</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;邮箱&#x27;</span>,</span><br><span class="line">  `password` <span class="type">varchar</span>(<span class="number">255</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;密码&#x27;</span>,</span><br><span class="line">  `salt` <span class="type">varchar</span>(<span class="number">50</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;盐值&#x27;</span>,</span><br><span class="line">  `createTime` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">  `updateTime` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB AUTO_INCREMENT<span class="operator">=</span><span class="number">17</span> <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 <span class="keyword">COLLATE</span><span class="operator">=</span>utf8mb4_0900_ai_ci COMMENT<span class="operator">=</span><span class="string">&#x27;用户表&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `t_user_info` (</span><br><span class="line">  `id` <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT COMMENT <span class="string">&#x27;主键&#x27;</span>,</span><br><span class="line">  `userId` <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;用户id&#x27;</span>,</span><br><span class="line">  `nick` <span class="type">varchar</span>(<span class="number">100</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;昵称&#x27;</span>,</span><br><span class="line">  `avatar` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;头像&#x27;</span>,</span><br><span class="line">  `sign` text COMMENT <span class="string">&#x27;签名&#x27;</span>,</span><br><span class="line">  `gender` <span class="type">varchar</span>(<span class="number">2</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;性别：0男 1女 2未知&#x27;</span>,</span><br><span class="line">  `birth` <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;生日&#x27;</span>,</span><br><span class="line">  `createTime` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">  `updateTime` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB AUTO_INCREMENT<span class="operator">=</span><span class="number">10</span> <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 <span class="keyword">COLLATE</span><span class="operator">=</span>utf8mb4_0900_ai_ci COMMENT<span class="operator">=</span><span class="string">&#x27;用户基本信息表&#x27;</span>;</span><br></pre></td></tr></table></figure><p>相关接口（API）：获取RSA公钥、用户注册、用户登录</p><blockquote><p>项目编写步骤：</p><p>entity(domain)-&gt;dao（@Mapper交给MyBatis）-&gt;service(注入dao)-&gt;api(controller)注入service[先留一个bug]</p><p>项目整体的逻辑：</p><p>前端访问到我们的api接口层（也就是控制层）之后，会跳转到相关的业务逻辑层（service），在业务实现逻辑层中可能会用到与数据库之间的交互，那么service就会去访问dao层（数据持久层，放的是与数据库进行交互的接口），dao层通过@Mapper注解与MyBatis产生关联，通过MyBatis进行交互，交互的结果返回给service业务逻辑层，业务逻辑层返回给控制层接口，然后在前端体现了出来。</p><p>以上层层嵌套的架构，可以让代码更加清晰、优雅。</p></blockquote><p>🆗，通过以上步骤，实体类已经新建好了，下面进行相关接口的业务逻辑书写</p><ul><li>获取RSA公钥</li></ul><p>@GetMapping</p><ul><li>用户注册</li></ul><p>@PostMapping</p><p>同样是按照接口的编写顺序来进行coding，在@Api中写接口，service中写实现，</p><ul><li>用户登录</li></ul><h3 id="基于JWT的用户token验证"><a href="#基于JWT的用户token验证" class="headerlink" title="基于JWT的用户token验证"></a>基于JWT的用户token验证</h3><p><code>JWT</code>:<code>JSON Web Token</code>，<code>JWT</code>是一个规范，用于在空间受限环境下安全传递”声明”。</p><p><strong>什么是声明呢？</strong></p><p>声明分为三个部分：</p><p>一、头部（header）</p><p>​ 声明的类型、声明的加密算法（通常使用SHA256）</p><p>二、载荷（payload）</p><p>​ 用于存放有效信息的，一般包含签发者、所面向的用户、接受方、过期时间、签发时间以及唯一身份标识（userId）</p><p>三、签名（signature）</p><p>​ 主要由头部、载荷、以及密钥组成加密而成</p><p>JWT的优点：</p><p>跨语言支持（因为<code>JWT</code>使用的是<code>JSON</code>数据格式，所以多语言都支持）、便于传输（见定义：空间受限的环境之下，说明<code>JWT</code>是数据量很小的，因此便于传输）、易于扩展（因为<code>JWT</code>有<code>payload</code>的部分，因为数据的分类很多、定制化强，可以通过<code>payload</code>进行数据添加，所以易于扩展）</p><p>说到session之前，先来说一下基于session的用户验证：</p><ul><li>基于session的用户身份验证</li><li>验证过程：服务端验证浏览器携带的用户名和密码，验证通过后生成用户凭证保存在服务端（session），浏览器再次访问时，服务端查询session，实现登陆状态保持。</li><li>缺点：随着用户的增多，服务端的压力增大；若浏览器的cookie被第三方或者攻击者拦截，容易受到跨站请求伪造攻击；分布式系统下扩展不强（多台服务器部署应用，用户在不同的服务器进行访问，因为session不会共享，所以不会进行自动登录）。</li></ul><p>说到session验证，再来说一下token验证：</p><ul><li>基于token的用户验证</li><li>验证过程：服务器端验证浏览器携带的用户名和密码，验证通过后，生成用户令牌（token），不同于session的一点是，服务端不会保存token，而会返回给浏览器，浏览器接收到token之后，进而写在浏览器的localstory中，那么什么是local story呢？localstory不同于cookie，它可以保存在本地，大小也比cookie大很多，所以在请求时就可以不用把token放在cookie中请求服务器，可以放在请求头中或者body中，这样就可以降低跨站请求拦截的风险，最后服务端拿到token之后进行校验是否正确，正确就证明是合法用户。</li><li>优点：token不存储在服务器，不会造成服务器的压力；token可以存储在非cookie中的（local story），安全性更高；分布式系统下扩展性较强（token生成之后返回前端，前端拿到之后在请求服务端，服务端再对token进行验证即可）。</li></ul></article><div class="mt-12 pt-6 border-t border-gray-200"><span class="bg-gray-100 dark:bg-gray-700 px-2 py-1 m-1 text-sm rounded-md transition-colors hover:bg-gray-200"><a href="/tags/SpringBoot/">SpringBoot</a> </span><span class="bg-gray-100 dark:bg-gray-700 px-2 py-1 m-1 text-sm rounded-md transition-colors hover:bg-gray-200"><a href="/tags/Mysql/">Mysql</a> </span><span class="bg-gray-100 dark:bg-gray-700 px-2 py-1 m-1 text-sm rounded-md transition-colors hover:bg-gray-200"><a href="/tags/MyBatis/">MyBatis</a> </span><span class="bg-gray-100 dark:bg-gray-700 px-2 py-1 m-1 text-sm rounded-md transition-colors hover:bg-gray-200"><a href="/tags/Maven/">Maven</a></span></div><div class="flex justify-between mt-12 pt-6 border-t border-gray-200"><div><a href="/posts/fd5f561d.html" class="text-sm text-gray-400 hover:text-gray-500 flex justify-center"><iconify-icon width="20" icon="ri:arrow-left-s-line" data-inline="false"></iconify-icon>MySQL笔记</a></div><div><a href="/posts/39f532c2.html" class="text-sm text-gray-400 hover:text-gray-500 flex justify-center">Vuepress 博客搭建<iconify-icon width="20" icon="ri:arrow-right-s-line" data-inline="false"></iconify-icon></a></div></div><div class="article-comments mt-12"><div class="giscus-container mt-8 pt-8 border-t border-gray-200 dark:border-gray-700"><script src="https://giscus.app/client.js" data-repo="WL2O2O/CS_GUIDER_Giscus" data-repo-id="R_kgDOJYdTQw" data-category="Announcements" data-category-id="DIC_kwDOJYdTQ84CWKC6" data-mapping="pathname" data-strict="0" data-reactions-enabled="1" data-emit-metadata="0" data-input-position="top" data-theme="preferred_color_scheme" data-lang="en" crossorigin="anonymous" async></script></div><script>function updateGiscusTheme(){const e=document.querySelector("iframe.giscus-frame");if(e){const s=document.documentElement.classList.contains("dark");e.contentWindow.postMessage({giscus:{setConfig:{theme:s?"dark":"light"}}},"https://giscus.app")}}window.addEventListener("message",e=>{"https://giscus.app"===e.origin&&e.data.giscus&&e.data.giscus.resizeHeight&&updateGiscusTheme()});const observer=new MutationObserver(()=>{updateGiscusTheme()});observer.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]})</script></div></section><script src="/lib/clipboard.min.js"></script><script async src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-MML-AM_CHTML"></script><script type="text/x-mathjax-config">MathJax.Hub.Config({
    "HTML-CSS": {
        preferredFont: "TeX",
        availableFonts: ["STIX","TeX"],
        linebreaks: { automatic:true },
        EqnChunk: (MathJax.Hub.Browser.isMobile ? 10 : 50)
    },
    tex2jax: {
        inlineMath: [ ["$", "$"], ["\\(","\\)"] ],
        processEscapes: true,
        ignoreClass: "tex2jax_ignore|dno",
        skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
    },
    TeX: {
        equationNumbers: { autoNumber: "AMS" },
        noUndefined: { attributes: { mathcolor: "red", mathbackground: "#FFEEEE", mathsize: "90%" } },
        Macros: { href: "{}" }
    },
    messageStyle: "none"
  });</script><script type="text/x-mathjax-config">MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });</script><script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script><script>$(document).ready(()=>{mermaid.initialize({theme:"default",logLevel:3,flowchart:{curve:"linear"},gantt:{axisFormat:"%m/%d/%Y"},sequence:{actorMargin:50}})})</script><script src="/lib/fancybox/fancybox.umd.min.js"></script><script>$(document).ready(()=>{$(".post-content").each((function(a){$(this).find("img").each((function(){if(!$(this).parent().hasClass("fancybox")&&!$(this).parent().is("a")){var a=this.alt;a&&$(this).after('<span class="fancybox-alt">'+a+"</span>"),$(this).wrap('<a class="fancybox-img" href="'+this.src+'" data-fancybox="gallery" data-caption="'+a+'"></a>')}})),$(this).find(".fancybox").each((function(){$(this).attr("rel","article"+a)}))})),Fancybox.bind('[data-fancybox="gallery"]',{})})</script><script src="/lib/tocbot/tocbot.min.js"></script><script>$(document).ready(()=>{tocbot.init({tocSelector:".post-toc",contentSelector:".post-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0})})</script></main><footer class="flex flex-col h-40 items-center justify-center text-gray-400 text-sm"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><div class="flex items-center gap-2"><span>Visitors</span> <span id="busuanzi_value_site_uv"></span> <span>Page Views</span> <span id="busuanzi_value_site_pv"></span></div><div class="flex items-center gap-2"><a target="_blank" href="https://creativecommons.org/licenses/by-nc-sa/4.0/" style="color:inherit">CC BY-NC-SA 4.0</a> <span>© 2025</span><iconify-icon width="18" icon="emojione-monotone:desktop-computer"></iconify-icon><a href="https://github.com/wl2o2o" target="_blank" rel="noopener noreferrer">CS_GUIDER</a></div><div class="flex items-center gap-2"><span>Powered by</span> <a href="https://hexo.io/" target="_blank" rel="noopener noreferrer">Hexo</a> <span>&</span> <a href="https://github.com/wl2o2o" target="_blank" rel="noopener noreferrer">CS_GUIDER</a></div></footer><div class="back-to-top box-border fixed right-6 z-1024 -bottom-20 rounded py-1 px-1 bg-slate-900 opacity-60 text-white cursor-pointer text-center dark:bg-slate-600"><span class="flex justify-center items-center text-sm"><iconify-icon width="18" icon="ion:arrow-up-c" id="go-top"></iconify-icon><span id="scrollpercent"><span>0</span> %</span></span></div><script src="/js/main.js"></script><script src="/js/search.js"></script><script>$(document).ready((function(){const t=document.getElementById("maple");Array.from({length:"10"}).map(()=>{const e=document.createElement("div"),a=.5*Math.random()+.5,l=2*Math.random()-1,n=Math.random()*t.clientWidth,i=-Math.random()*t.clientHeight;return e.className="maple",e.style.width=24*a+"px",e.style.height=24*a+"px",e.style.left=n+"px",e.style.top=i+"px",e.style.setProperty("--maple-fall-offset",l),e.style.setProperty("--maple-fall-height",Math.abs(i)+t.clientHeight+"px"),e.style.animation=`fall ${10/"0.3"}s linear infinite`,e.style.animationDelay=-10/"0.3"+"s",t.appendChild(e),e})}))</script><div class="fixed top-0 bottom-0 left-0 right-0 pointer-events-none print:hidden" id="maple"></div></body></html>